package com.region.http.client.request.okhttp;

import com.region.common.adapter.json.JSONAdapterFactory;
import com.region.common.adapter.logging.LoggerAdapter;
import com.region.common.adapter.logging.LoggerAdapterFactory;
import com.region.common.utils.RandomUtils;
import com.region.http.client.common.Constant;
import com.region.http.client.common.ContentType;
import com.region.http.client.common.RequestType;
import com.region.http.client.config.RequestAnnotationConfig;
import com.region.http.client.exception.ResponseErrorException;
import com.region.http.client.model.MultiFileTransferMeta;
import com.region.http.client.model.RequestParameter;
import com.region.http.client.model.ResponseData;
import com.region.http.client.request.AbstractTemplateHttpExecutor;
import com.region.http.client.utils.IOUtils;
import okhttp3.*;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.File;
import java.io.InputStream;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * @author liujieyu
 * @date 2024/9/30 14:38
 * @desciption
 */
public class Ok3HttpExecutor extends AbstractTemplateHttpExecutor {

    private final LoggerAdapter loggerAdapter = LoggerAdapterFactory.getLogger(getClass());

    @Override
    public ResponseData execute(String url, RequestAnnotationConfig requestAnnotationConfig, RequestParameter parameters) throws Throwable {
        OkHttpClient okHttpClient = getOkHttpClient(url, requestAnnotationConfig);
        Request request = createOkRequest(url, requestAnnotationConfig, parameters);
        try (Response response = okHttpClient.newCall(request).execute()) {
            if (response.isSuccessful()) {
                ResponseData responseData = ResponseData.getInstance();
                // Set body
                responseData.setBody(response.body().bytes());
                // Set response header
                Headers headers = response.headers();
                Set<String> names = headers.names();
                for(String headerName : names) {
                    List<String> values = headers.values(headerName);
                    for (int i = 0; i < values.size(); i++) {
                        String value = values.get(i);
                        responseData.addHeader(headerName, value);
                        String[] element = value.split(";");
                        for (int j = 0; j < element.length; j++) {
                            String[] args = element[j].split("=");
                            if (args.length == 2) {
                                responseData.addNameValuePair(headerName, args[0].trim(), args[1].trim());
                            }
                            if (args.length == 1) {
                                responseData.addNameValuePair(headerName, args[0].trim(), null);
                            }
                        }
                    }
                }
                return responseData;
            } else {
                String msg = "The ok3 http execute error, response code:" + response.code();
                loggerAdapter.error(msg);
                throw new ResponseErrorException(msg);
            }
        } catch (Throwable throwable) {
            loggerAdapter.error("The request execute fail.", throwable);
            throw throwable;
        }
    }

    private OkHttpClient getOkHttpClient(String url, RequestAnnotationConfig requestAnnotationConfig) {
        OkHttpClient.Builder builder = new OkHttpClient
                .Builder()
                .connectTimeout(requestAnnotationConfig.getConnectTime(), TimeUnit.MILLISECONDS)
                .readTimeout(requestAnnotationConfig.getReadTime(), TimeUnit.MILLISECONDS)
                .callTimeout(requestAnnotationConfig.getRequestTime(), TimeUnit.MILLISECONDS);
        if (url.startsWith(Constant.HTTPS_PREFIX)) {
            try {
                TrustManager trustManager = new X509TrustManager() {
                    @Override
                    public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
                        // do nothing.
                    }
                    @Override
                    public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
                        // do nothing.
                    }
                    @Override
                    public X509Certificate[] getAcceptedIssuers() {
                        return new X509Certificate[]{};
                    }
                };
                SSLContext sslContext = SSLContext.getInstance("SSL");
                sslContext.init(null, new TrustManager[]{trustManager}, new java.security.SecureRandom());
                builder.hostnameVerifier((s, sslSession) -> true);
                builder.sslSocketFactory(sslContext.getSocketFactory(), (X509TrustManager) trustManager);
            } catch (Exception e) {
                loggerAdapter.error("The create Https URL connection fail.", e);
            }
        }
        return builder.build();
    }

    private Request createOkRequest(String url, RequestAnnotationConfig requestAnnotationConfig, RequestParameter parameters) {
        Request.Builder builder = new Request.Builder();
        // Set header
        setHeaderValue(builder, parameters.getHeaders());
        // Set request parameter
        setParameter(builder, url, requestAnnotationConfig.getRequestType(), requestAnnotationConfig.getContentType(), parameters);
        return builder.build();
    }

    private void setHeaderValue(Request.Builder builder, Map<String, Object> headers) {
        if (headers == null || headers.isEmpty()) {
            return;
        }
        for (Map.Entry<String, Object> entry : headers.entrySet()) {
            builder.addHeader(entry.getKey(), String.valueOf(entry.getValue()));
        }
    }

    private void setParameter(Request.Builder builder, String url, RequestType requestType, ContentType contentType, RequestParameter parameters) {
        if (ContentType.X_WWW_FORM_URLENCODED == contentType) {
            url = processUrl(parameters.getForm(), url);
        }
        switch (requestType) {
            case POST:
                builder.post(createBody(contentType, parameters));
                break;
            case PUT:
                builder.put(createBody(contentType, parameters));
                break;
            case HEAD:
                builder.head();
                break;
            case DELETE:
                builder.delete();
                break;
            case TRACE:
            case OPTIONS:
                // no support
                break;
            default:
                builder.get();
        }
        // Set request url
        builder.url(url);
    }

    /**
     * create request body
     *
     * @param contentType
     * @param parameters
     * @return
     */
    private RequestBody createBody(ContentType contentType, RequestParameter parameters) {
        RequestBody requestBody = null;
        if (ContentType.MULTIPART_FORM_DATA_VALUE == contentType) {
            if (existsFileType(parameters)) {
                MultipartBody.Builder builder = new MultipartBody.Builder();
                builder.setType(MultipartBody.FORM);
                Map<String, Object> form = parameters.getForm();
                if (form != null && !form.isEmpty()) {
                    for (Map.Entry<String, Object> entry : form.entrySet()) {
                        String key = entry.getKey();
                        Object value = entry.getValue();
                        if (value instanceof File) {
                            File file = (File) value;
                            builder.addFormDataPart(key, file.getName(),
                                    RequestBody.create(file, MediaType.get(ContentType.APPLICATION_OCTET_STREAM_VALUE.getValue())));
                        } else if (value instanceof byte[]) {
                            byte[] bytes = (byte[]) value;
                            builder.addFormDataPart(key, RandomUtils.getId(),
                                    RequestBody.create(bytes, MediaType.get(ContentType.APPLICATION_OCTET_STREAM_VALUE.getValue())));
                        } else if (value instanceof InputStream) {
                            InputStream inputStream = (InputStream) value;
                            builder.addFormDataPart(key, RandomUtils.getId(),
                                    RequestBody.create(IOUtils.convertInputStreamToBytes(inputStream), MediaType.get(ContentType.APPLICATION_OCTET_STREAM_VALUE.getValue())));
                        } else if (value instanceof MultiFileTransferMeta) {
                            MultiFileTransferMeta transferMeta = (MultiFileTransferMeta) value;
                            // process multipart file
                            if (transferMeta.getParameters() != null) {
                                for (Map.Entry<String, Object> data : transferMeta.getParameters().entrySet()) {
                                    String name = data.getKey();
                                    Object fileValue = data.getValue();
                                    if (fileValue instanceof byte[]) {
                                        byte[] bytes = (byte[]) fileValue;
                                        builder.addFormDataPart(key, RandomUtils.getId(),
                                                RequestBody.create(bytes, MediaType.get(ContentType.APPLICATION_OCTET_STREAM_VALUE.getValue())));
                                    }
                                    if (fileValue instanceof InputStream) {
                                        InputStream inputStream = (InputStream) fileValue;
                                        builder.addFormDataPart(key, RandomUtils.getId(),
                                                RequestBody.create(IOUtils.convertInputStreamToBytes(inputStream), MediaType.get(ContentType.APPLICATION_OCTET_STREAM_VALUE.getValue())));
                                    }
                                    if (fileValue instanceof File) {
                                        File file = (File) fileValue;
                                        builder.addFormDataPart(key, file.getName(),
                                                RequestBody.create(file, MediaType.get(ContentType.APPLICATION_OCTET_STREAM_VALUE.getValue())));
                                    }
                                }
                            }
                        } else {
                            builder.addFormDataPart(key, String.valueOf(value));
                        }
                    }
                    requestBody = builder.build();
                }
            } else  if (ContentType.APPLICATION_JSON == contentType || ContentType.APPLICATION_JSON_UTF8 == contentType) {
                Map<String, Object> form = parameters.getForm();
                if (form != null && !form.isEmpty()) {
                    FormBody.Builder formBuilder = new FormBody.Builder();
                    // 将 Map 中的键值对添加到请求体中
                    for (Map.Entry<String, Object> entry : form.entrySet()) {
                        formBuilder.add(entry.getKey(), String.valueOf(entry.getValue()));
                    }
                    requestBody = formBuilder.build();
                }
            }
        } else {
            if (parameters.getBody() != null) {
                requestBody = RequestBody.create(JSONAdapterFactory.getAdapter().toJSONString(parameters.getBody()), MediaType.parse(contentType.getValue()));
            }
        }
        if (requestBody == null) {
            requestBody = RequestBody.create("", null);
        }
        return requestBody;
    }

}
